/**
 * Project: d20Campaigner
 * Created: Oct 25, 2007 by bebopJMM
 */
package org.rollinitiative.d20.entity;

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Hashtable;

import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlbeans.XmlException;
import org.exist.xmldb.XQueryService;
import org.rollinitiative.d20.Adjustment;
import org.rollinitiative.d20.Adjustment.AdjustmentSource;
import org.rollinitiative.d20.persist.PersistenceBridge;
import org.rollinitiative.d20.schema.race.AbilityModListType;
import org.rollinitiative.d20.schema.race.AbilityModType;
import org.rollinitiative.d20.schema.race.LevelType;
import org.rollinitiative.d20.schema.race.MovementType;
import org.rollinitiative.d20.schema.race.RaceDocument;
import org.rollinitiative.d20.schema.race.RaceType;
import org.rollinitiative.d20.schema.skill.TrainingType;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;

/**
 * This class bridges the domains of data content and data persistence for Race related content.
 * 
 * @author bebopjmm
 * 
 */
public class RaceContentBridge extends PersistenceBridge
{
    final static Log LOG = LogFactory.getLog(RaceContentBridge.class);

    HashMap<String, RaceType> raceCache = new HashMap<String, RaceType>();

    Collection racesCollection;


    public RaceContentBridge(XMLConfiguration config)
    {
        super(config);
    }


    /**
     * Returns the Size enumeration value of the race specified by raceID.
     * 
     * @param raceID
     * @return Size of the Race specified.
     * @throws InvalidEntityException when the specified raceID cannot be retrieved.
     */
    public Size getSize(String raceID) throws InvalidEntityException
    {
        RaceType race = retrieveRace(raceID);
        return Size.mapSizeEnum(race.xgetSize());
    }


    /**
     * Returns the display friendly name of the race specified by raceID.
     * 
     * @param raceID unique identifier of Race to be queried
     * @return Display name of the Race
     * @throws InvalidEntityException when the specified raceID cannot be retrieved.
     */
    public String getName(String raceID) throws InvalidEntityException
    {
        RaceType race = retrieveRace(raceID);
        return race.getName();
    }


    /**
     * This method returns the table of racial AbilityAdjustments, keyed by Ability, for the
     * designated race.
     * 
     * @param raceID
     * @return Hashtable of AbilityAdjustments
     * @throws InvalidEntityException when the specified raceID cannot be retrieved
     */
    public Hashtable<Ability, Adjustment> getAbilityMods(String raceID)
            throws InvalidEntityException
    {
        Hashtable<Ability, Adjustment> adjustmentTable = new Hashtable<Ability, Adjustment>();
        RaceType race = retrieveRace(raceID);
        AbilityModListType modList = race.getAbilityModifierList();
        if (modList.isNil()) {
            LOG.debug("No ability mods for raceID: " + raceID);
        }
        else {
            for (AbilityModType abilityModType : modList.getAbilityModifierArray()) {
                adjustmentTable.put(Ability.mapAbility(abilityModType.xgetAbility()),
                        new Adjustment(AdjustmentSource.INHERENT, abilityModType.getModifier(),
                                "Racial"));
            }
        }
        return adjustmentTable;
    }


    /**
     * This method assembles a table of racial adjustments to skills, keyed by the Skill id, for the
     * designated race at the designated level.
     * 
     * @param raceID
     * @param level
     * @return Hashtable of Adjustments to skills
     * @throws InvalidEntityException when the specified raceID cannot be retrieved
     */
    public Hashtable<String, Adjustment> getSkillMods(String raceID, int level)
            throws InvalidEntityException
    {
        Hashtable<String, Adjustment> adjustmentTable = new Hashtable<String, Adjustment>();
        RaceType race = retrieveRace(raceID);
        LevelType[] levels = race.getAdvancement().getLevelEntryArray();
        TrainingType[] bonuses = null;
        Adjustment racialSkillBonus = null;
        for (int i = 0; i < level; i++) {
            bonuses = levels[i].getSkillBonuses().getSkillTrainedArray();
            for (TrainingType trainingType : bonuses) {
                LOG.info("At level " + (i+1) + ", " + raceID + " has skill bonus: "
                        + trainingType.getSkillID().getLocalPart() + " = " + trainingType.getPoints().intValue());
                if (adjustmentTable.containsKey(trainingType.getSkillID().getLocalPart())) {
                    racialSkillBonus = adjustmentTable
                            .get(trainingType.getSkillID().getLocalPart());
                    int value = racialSkillBonus.getValue();
                    value += trainingType.getPoints().intValue();
                    racialSkillBonus.setValue(value);
                }
                else {
                    racialSkillBonus = new Adjustment(AdjustmentSource.INHERENT, trainingType
                            .getPoints().intValue(), "Racial." + raceID);
                    adjustmentTable.put(trainingType.getSkillID().getLocalPart(), racialSkillBonus);
                }
                
            }
        }
        return adjustmentTable;
    }


    public int getSpeed(String raceID, Movement.EncumberanceType encumberance)
            throws InvalidEntityException
    {
        RaceType race = retrieveRace(raceID);
        MovementType movement = race.getMovement();
        switch (encumberance) {
        case LIGHT:
            return movement.getLight().intValue();

        case MEDIUM:
            return movement.getMedium().intValue();

        case HEAVY:
            return movement.getHeavy().intValue();

        default:
            return 0;
        }
    }


    /**
     * This method unmarshalls an xml Race into it's xmlBean from a sourceFile.
     * 
     * @param sourceFile
     * @return
     */
    RaceType loadRace(File sourceFile)
    {
        // Attempt to construct xmlBean from document.
        LOG.info("Attempting to unmarshall: " + sourceFile.getName());
        FileInputStream fileStream = null;
        try {
            // Unmarshall the xml.
            fileStream = new FileInputStream(sourceFile);
            RaceDocument raceDoc = RaceDocument.Factory.parse(fileStream);
            fileStream.close();
            return raceDoc.getRace();
        } catch (Throwable ex) {
            LOG.error("Failed to load Race from: " + sourceFile, ex);
            return null;
        }
    }


    RaceType queryRace(String raceName)
    {
        // TODO: XMLQuery for RaceType matching provided raceName
        return null;
    }


    /**
     * This method returns the RaceType of the designated race. It first checks a local cache and
     * secondly the persistent store.
     * 
     * @param raceID
     * @return RaceType of the specified raceID
     * @throws InvalidEntityException
     */
    RaceType retrieveRace(String raceID) throws InvalidEntityException
    {
        // Check Cache first
        if (raceCache.containsKey(raceID)) {
            LOG.debug("Cache contained requested race: " + raceID);
            return raceCache.get(raceID);
        }

        try {
            LOG.debug("Querying for requested race: " + raceID);
            String raceQuery = config.getString("raceBridge.queries.raceByID");
            raceQuery = raceQuery.replace("#ID#", raceID);
            LOG.debug("Parameterized Query: " + raceQuery);
            ResourceSet results = query(raceQuery);
            if (results.getSize() < 1) {
                throw new InvalidEntityException("Specified Race not Found: " + raceID);
            }

            RaceType race = RaceType.Factory.parse(results.getResource(0).getContent().toString(),
                    xmlOptions);
            raceCache.put(raceID, race);
            return race;
        } catch (XmlException e) {
            LOG.error("Failure to parse requested race: )" + raceID, e);
            throw new InvalidEntityException("Failure to retrieve specified Race: " + raceID);
        } catch (XMLDBException e) {
            LOG.error("Failure to parse requested race: )" + raceID, e);
            throw new InvalidEntityException("Failure to retrieve specified Race: " + raceID);
        }
    }


    /**
     * Retrieves the race collection for the specified campaign.
     * 
     * @param campaign
     */
    public void loadCollection(String campaign)
    {
        try {
            connect(campaign);
            racesCollection = retrieveCollection("races");
            xqueryService = (XQueryService) racesCollection.getService("XQueryService", "1.0");
        } catch (XMLDBException ex) {
            LOG.error("Failed to load the races collection");
        }
    }
}
